/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactClass
 */

'use strict'

var _assign = require('object-assign')

var ReactComponent = require('./ReactComponent')
var ReactElement = require('./ReactElement')
var ReactPropTypeLocations = require('./ReactPropTypeLocations')
var ReactPropTypeLocationNames = require('./ReactPropTypeLocationNames')
var ReactNoopUpdateQueue = require('./ReactNoopUpdateQueue')

var emptyObject = require('fbjs/lib/emptyObject')
var invariant = require('fbjs/lib/invariant')
var keyMirror = require('fbjs/lib/keyMirror')
var keyOf = require('fbjs/lib/keyOf')
var warning = require('fbjs/lib/warning')

var MIXINS_KEY = keyOf({ mixins: null })

/**
 * Policies that describe methods in `ReactClassInterface`.
 */
var SpecPolicy = keyMirror({
  /**
   * These methods may be defined only once by the class specification or mixin.
   */
  DEFINE_ONCE: null,
  /**
   * These methods may be defined by both the class specification and mixins.
   * Subsequent definitions will be chained. These methods must return void.
   */
  DEFINE_MANY: null,
  /**
   * These methods are overriding the base class.
   */
  OVERRIDE_BASE: null,
  /**
   * These methods are similar to DEFINE_MANY, except we assume they return
   * objects. We try to merge the keys of the return values of all the mixed in
   * functions. If there is a key conflict we throw.
   */
  DEFINE_MANY_MERGED: null
})

var injectedMixins = []

/**
 * Composite components are higher-level components that compose other composite
 * or native components.
 *
 * To create a new type of `ReactClass`, pass a specification of
 * your new class to `React.createClass`. The only requirement of your class
 * specification is that you implement a `render` method.
 *
 *   var MyComponent = React.createClass({
 *     render: function() {
 *       return <div>Hello World</div>;
 *     }
 *   });
 *
 * The class specification supports a specific protocol of methods that have
 * special meaning (e.g. `render`). See `ReactClassInterface` for
 * more the comprehensive protocol. Any other properties and methods in the
 * class specification will be available on the prototype.
 *
 * @interface ReactClassInterface
 * @internal
 */
var ReactClassInterface = {
  /**
   * An array of Mixin objects to include when defining your component.
   *
   * @type {array}
   * @optional
   */
  mixins: SpecPolicy.DEFINE_MANY,

  /**
   * An object containing properties and methods that should be defined on
   * the component's constructor instead of its prototype (static methods).
   *
   * @type {object}
   * @optional
   */
  statics: SpecPolicy.DEFINE_MANY,

  /**
   * Definition of prop types for this component.
   *
   * @type {object}
   * @optional
   */
  propTypes: SpecPolicy.DEFINE_MANY,

  /**
   * Definition of context types for this component.
   *
   * @type {object}
   * @optional
   */
  contextTypes: SpecPolicy.DEFINE_MANY,

  /**
   * Definition of context types this component sets for its children.
   *
   * @type {object}
   * @optional
   */
  childContextTypes: SpecPolicy.DEFINE_MANY,

  // ==== Definition methods ====

  /**
   * Invoked when the component is mounted. Values in the mapping will be set on
   * `this.props` if that prop is not specified (i.e. using an `in` check).
   *
   * This method is invoked before `getInitialState` and therefore cannot rely
   * on `this.state` or use `this.setState`.
   *
   * @return {object}
   * @optional
   */
  getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED,

  /**
   * Invoked once before the component is mounted. The return value will be used
   * as the initial value of `this.state`.
   *
   *   getInitialState: function() {
   *     return {
   *       isOn: false,
   *       fooBaz: new BazFoo()
   *     }
   *   }
   *
   * @return {object}
   * @optional
   */
  getInitialState: SpecPolicy.DEFINE_MANY_MERGED,

  /**
   * @return {object}
   * @optional
   */
  getChildContext: SpecPolicy.DEFINE_MANY_MERGED,

  /**
   * Uses props from `this.props` and state from `this.state` to render the
   * structure of the component.
   *
   * No guarantees are made about when or how often this method is invoked, so
   * it must not have side effects.
   *
   *   render: function() {
   *     var name = this.props.name;
   *     return <div>Hello, {name}!</div>;
   *   }
   *
   * @return {ReactComponent}
   * @nosideeffects
   * @required
   */
  render: SpecPolicy.DEFINE_ONCE,

  // ==== Delegate methods ====

  /**
   * Invoked when the component is initially created and about to be mounted.
   * This may have side effects, but any external subscriptions or data created
   * by this method must be cleaned up in `componentWillUnmount`.
   *
   * @optional
   */
  componentWillMount: SpecPolicy.DEFINE_MANY,

  /**
   * Invoked when the component has been mounted and has a DOM representation.
   * However, there is no guarantee that the DOM node is in the document.
   *
   * Use this as an opportunity to operate on the DOM when the component has
   * been mounted (initialized and rendered) for the first time.
   *
   * @param {DOMElement} rootNode DOM element representing the component.
   * @optional
   */
  componentDidMount: SpecPolicy.DEFINE_MANY,

  /**
   * Invoked before the component receives new props.
   *
   * Use this as an opportunity to react to a prop transition by updating the
   * state using `this.setState`. Current props are accessed via `this.props`.
   *
   *   componentWillReceiveProps: function(nextProps, nextContext) {
   *     this.setState({
   *       likesIncreasing: nextProps.likeCount > this.props.likeCount
   *     });
   *   }
   *
   * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
   * transition may cause a state change, but the opposite is not true. If you
   * need it, you are probably looking for `componentWillUpdate`.
   *
   * @param {object} nextProps
   * @optional
   */
  componentWillReceiveProps: SpecPolicy.DEFINE_MANY,

  /**
   * Invoked while deciding if the component should be updated as a result of
   * receiving new props, state and/or context.
   *
   * Use this as an opportunity to `return false` when you're certain that the
   * transition to the new props/state/context will not require a component
   * update.
   *
   *   shouldComponentUpdate: function(nextProps, nextState, nextContext) {
   *     return !equal(nextProps, this.props) ||
   *       !equal(nextState, this.state) ||
   *       !equal(nextContext, this.context);
   *   }
   *
   * @param {object} nextProps
   * @param {?object} nextState
   * @param {?object} nextContext
   * @return {boolean} True if the component should update.
   * @optional
   */
  shouldComponentUpdate: SpecPolicy.DEFINE_ONCE,

  /**
   * Invoked when the component is about to update due to a transition from
   * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
   * and `nextContext`.
   *
   * Use this as an opportunity to perform preparation before an update occurs.
   *
   * NOTE: You **cannot** use `this.setState()` in this method.
   *
   * @param {object} nextProps
   * @param {?object} nextState
   * @param {?object} nextContext
   * @param {ReactReconcileTransaction} transaction
   * @optional
   */
  componentWillUpdate: SpecPolicy.DEFINE_MANY,

  /**
   * Invoked when the component's DOM representation has been updated.
   *
   * Use this as an opportunity to operate on the DOM when the component has
   * been updated.
   *
   * @param {object} prevProps
   * @param {?object} prevState
   * @param {?object} prevContext
   * @param {DOMElement} rootNode DOM element representing the component.
   * @optional
   */
  componentDidUpdate: SpecPolicy.DEFINE_MANY,

  /**
   * Invoked when the component is about to be removed from its parent and have
   * its DOM representation destroyed.
   *
   * Use this as an opportunity to deallocate any external resources.
   *
   * NOTE: There is no `componentDidUnmount` since your component will have been
   * destroyed by that point.
   *
   * @optional
   */
  componentWillUnmount: SpecPolicy.DEFINE_MANY,

  // ==== Advanced methods ====

  /**
   * Updates the component's currently mounted DOM representation.
   *
   * By default, this implements React's rendering and reconciliation algorithm.
   * Sophisticated clients may wish to override this.
   *
   * @param {ReactReconcileTransaction} transaction
   * @internal
   * @overridable
   */
  updateComponent: SpecPolicy.OVERRIDE_BASE
}

/**
 * Mapping from class specification keys to special processing functions.
 *
 * Although these are declared like instance properties in the specification
 * when defining classes using `React.createClass`, they are actually static
 * and are accessible on the constructor instead of the prototype. Despite
 * being static, they must be defined outside of the "statics" key under
 * which all other static methods are defined.
 */
var RESERVED_SPEC_KEYS = {
  displayName: function (Constructor, displayName) {
    Constructor.displayName = displayName
  },
  mixins: function (Constructor, mixins) {
    if (mixins) {
      for (var i = 0; i < mixins.length; i++) {
        mixSpecIntoComponent(Constructor, mixins[i])
      }
    }
  },
  childContextTypes: function (Constructor, childContextTypes) {
    if (process.env.NODE_ENV !== 'production') {
      validateTypeDef(
        Constructor,
        childContextTypes,
        ReactPropTypeLocations.childContext
      )
    }
    Constructor.childContextTypes = _assign(
      {},
      Constructor.childContextTypes,
      childContextTypes
    )
  },
  contextTypes: function (Constructor, contextTypes) {
    if (process.env.NODE_ENV !== 'production') {
      validateTypeDef(Constructor, contextTypes, ReactPropTypeLocations.context)
    }
    Constructor.contextTypes = _assign(
      {},
      Constructor.contextTypes,
      contextTypes
    )
  },
  /**
   * Special case getDefaultProps which should move into statics but requires
   * automatic merging.
   */
  getDefaultProps: function (Constructor, getDefaultProps) {
    if (Constructor.getDefaultProps) {
      Constructor.getDefaultProps = createMergedResultFunction(
        Constructor.getDefaultProps,
        getDefaultProps
      )
    } else {
      Constructor.getDefaultProps = getDefaultProps
    }
  },
  propTypes: function (Constructor, propTypes) {
    if (process.env.NODE_ENV !== 'production') {
      validateTypeDef(Constructor, propTypes, ReactPropTypeLocations.prop)
    }
    Constructor.propTypes = _assign({}, Constructor.propTypes, propTypes)
  },
  statics: function (Constructor, statics) {
    mixStaticSpecIntoComponent(Constructor, statics)
  },
  autobind: function () {}
}

// noop
function validateTypeDef(Constructor, typeDef, location) {
  for (var propName in typeDef) {
    if (typeDef.hasOwnProperty(propName)) {
      // use a warning instead of an invariant so components
      // don't show up in prod but only in __DEV__
      process.env.NODE_ENV !== 'production'
        ? warning(
            typeof typeDef[propName] === 'function',
            '%s: %s type `%s` is invalid; it must be a function, usually from ' +
              'React.PropTypes.',
            Constructor.displayName || 'ReactClass',
            ReactPropTypeLocationNames[location],
            propName
          )
        : void 0
    }
  }
}

function validateMethodOverride(isAlreadyDefined, name) {
  var specPolicy = ReactClassInterface.hasOwnProperty(name)
    ? ReactClassInterface[name]
    : null

  // Disallow overriding of base class methods unless explicitly allowed.
  if (ReactClassMixin.hasOwnProperty(name)) {
    !(specPolicy === SpecPolicy.OVERRIDE_BASE)
      ? process.env.NODE_ENV !== 'production'
        ? invariant(
            false,
            'ReactClassInterface: You are attempting to override ' +
              '`%s` from your class specification. Ensure that your method names ' +
              'do not overlap with React methods.',
            name
          )
        : invariant(false)
      : void 0
  }

  // Disallow defining methods more than once unless explicitly allowed.
  if (isAlreadyDefined) {
    !(
      specPolicy === SpecPolicy.DEFINE_MANY ||
      specPolicy === SpecPolicy.DEFINE_MANY_MERGED
    )
      ? process.env.NODE_ENV !== 'production'
        ? invariant(
            false,
            'ReactClassInterface: You are attempting to define ' +
              '`%s` on your component more than once. This conflict may be due ' +
              'to a mixin.',
            name
          )
        : invariant(false)
      : void 0
  }
}

/**
 * Mixin helper which handles policy validation and reserved
 * specification keys when building React classes.
 */
function mixSpecIntoComponent(Constructor, spec) {
  if (!spec) {
    return
  }

  !(typeof spec !== 'function')
    ? process.env.NODE_ENV !== 'production'
      ? invariant(
          false,
          "ReactClass: You're attempting to " +
            'use a component class or function as a mixin. Instead, just use a ' +
            'regular object.'
        )
      : invariant(false)
    : void 0
  !!ReactElement.isValidElement(spec)
    ? process.env.NODE_ENV !== 'production'
      ? invariant(
          false,
          "ReactClass: You're attempting to " +
            'use a component as a mixin. Instead, just use a regular object.'
        )
      : invariant(false)
    : void 0

  var proto = Constructor.prototype
  var autoBindPairs = proto.__reactAutoBindPairs

  // By handling mixins before any other properties, we ensure the same
  // chaining order is applied to methods with DEFINE_MANY policy, whether
  // mixins are listed before or after these methods in the spec.
  if (spec.hasOwnProperty(MIXINS_KEY)) {
    RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins)
  }

  for (var name in spec) {
    if (!spec.hasOwnProperty(name)) {
      continue
    }

    if (name === MIXINS_KEY) {
      // We have already handled mixins in a special case above.
      continue
    }

    var property = spec[name]
    var isAlreadyDefined = proto.hasOwnProperty(name)
    validateMethodOverride(isAlreadyDefined, name)

    if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
      RESERVED_SPEC_KEYS[name](Constructor, property)
    } else {
      // Setup methods on prototype:
      // The following member methods should not be automatically bound:
      // 1. Expected ReactClass methods (in the "interface").
      // 2. Overridden methods (that were mixed in).
      var isReactClassMethod = ReactClassInterface.hasOwnProperty(name)
      var isFunction = typeof property === 'function'
      var shouldAutoBind =
        isFunction &&
        !isReactClassMethod &&
        !isAlreadyDefined &&
        spec.autobind !== false

      if (shouldAutoBind) {
        autoBindPairs.push(name, property)
        proto[name] = property
      } else {
        if (isAlreadyDefined) {
          var specPolicy = ReactClassInterface[name]

          // These cases should already be caught by validateMethodOverride.
          !(
            isReactClassMethod &&
            (specPolicy === SpecPolicy.DEFINE_MANY_MERGED ||
              specPolicy === SpecPolicy.DEFINE_MANY)
          )
            ? process.env.NODE_ENV !== 'production'
              ? invariant(
                  false,
                  'ReactClass: Unexpected spec policy %s for key %s ' +
                    'when mixing in component specs.',
                  specPolicy,
                  name
                )
              : invariant(false)
            : void 0

          // For methods which are defined more than once, call the existing
          // methods before calling the new property, merging if appropriate.
          if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) {
            proto[name] = createMergedResultFunction(proto[name], property)
          } else if (specPolicy === SpecPolicy.DEFINE_MANY) {
            proto[name] = createChainedFunction(proto[name], property)
          }
        } else {
          proto[name] = property
          if (process.env.NODE_ENV !== 'production') {
            // Add verbose displayName to the function, which helps when looking
            // at profiling tools.
            if (typeof property === 'function' && spec.displayName) {
              proto[name].displayName = spec.displayName + '_' + name
            }
          }
        }
      }
    }
  }
}

function mixStaticSpecIntoComponent(Constructor, statics) {
  if (!statics) {
    return
  }
  for (var name in statics) {
    var property = statics[name]
    if (!statics.hasOwnProperty(name)) {
      continue
    }

    var isReserved = name in RESERVED_SPEC_KEYS
    !!isReserved
      ? process.env.NODE_ENV !== 'production'
        ? invariant(
            false,
            'ReactClass: You are attempting to define a reserved ' +
              'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
              'as an instance property instead; it will still be accessible on the ' +
              'constructor.',
            name
          )
        : invariant(false)
      : void 0

    var isInherited = name in Constructor
    !!isInherited
      ? process.env.NODE_ENV !== 'production'
        ? invariant(
            false,
            'ReactClass: You are attempting to define ' +
              '`%s` on your component more than once. This conflict may be ' +
              'due to a mixin.',
            name
          )
        : invariant(false)
      : void 0
    Constructor[name] = property
  }
}

/**
 * Merge two objects, but throw if both contain the same key.
 *
 * @param {object} one The first object, which is mutated.
 * @param {object} two The second object
 * @return {object} one after it has been mutated to contain everything in two.
 */
function mergeIntoWithNoDuplicateKeys(one, two) {
  !(one && two && typeof one === 'object' && typeof two === 'object')
    ? process.env.NODE_ENV !== 'production'
      ? invariant(
          false,
          'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'
        )
      : invariant(false)
    : void 0

  for (var key in two) {
    if (two.hasOwnProperty(key)) {
      !(one[key] === undefined)
        ? process.env.NODE_ENV !== 'production'
          ? invariant(
              false,
              'mergeIntoWithNoDuplicateKeys(): ' +
                'Tried to merge two objects with the same key: `%s`. This conflict ' +
                'may be due to a mixin; in particular, this may be caused by two ' +
                'getInitialState() or getDefaultProps() methods returning objects ' +
                'with clashing keys.',
              key
            )
          : invariant(false)
        : void 0
      one[key] = two[key]
    }
  }
  return one
}

/**
 * Creates a function that invokes two functions and merges their return values.
 *
 * @param {function} one Function to invoke first.
 * @param {function} two Function to invoke second.
 * @return {function} Function that invokes the two argument functions.
 * @private
 */
function createMergedResultFunction(one, two) {
  return function mergedResult() {
    var a = one.apply(this, arguments)
    var b = two.apply(this, arguments)
    if (a == null) {
      return b
    } else if (b == null) {
      return a
    }
    var c = {}
    mergeIntoWithNoDuplicateKeys(c, a)
    mergeIntoWithNoDuplicateKeys(c, b)
    return c
  }
}

/**
 * Creates a function that invokes two functions and ignores their return vales.
 *
 * @param {function} one Function to invoke first.
 * @param {function} two Function to invoke second.
 * @return {function} Function that invokes the two argument functions.
 * @private
 */
function createChainedFunction(one, two) {
  return function chainedFunction() {
    one.apply(this, arguments)
    two.apply(this, arguments)
  }
}

/**
 * Binds a method to the component.
 *
 * @param {object} component Component whose method is going to be bound.
 * @param {function} method Method to be bound.
 * @return {function} The bound method.
 */
function bindAutoBindMethod(component, method) {
  var boundMethod = method.bind(component)
  if (process.env.NODE_ENV !== 'production') {
    boundMethod.__reactBoundContext = component
    boundMethod.__reactBoundMethod = method
    boundMethod.__reactBoundArguments = null
    var componentName = component.constructor.displayName
    var _bind = boundMethod.bind
    boundMethod.bind = function (newThis) {
      for (
        var _len = arguments.length,
          args = Array(_len > 1 ? _len - 1 : 0),
          _key = 1;
        _key < _len;
        _key++
      ) {
        args[_key - 1] = arguments[_key]
      }

      // User is trying to bind() an autobound method; we effectively will
      // ignore the value of "this" that the user is trying to use, so
      // let's warn.
      if (newThis !== component && newThis !== null) {
        process.env.NODE_ENV !== 'production'
          ? warning(
              false,
              'bind(): React component methods may only be bound to the ' +
                'component instance. See %s',
              componentName
            )
          : void 0
      } else if (!args.length) {
        process.env.NODE_ENV !== 'production'
          ? warning(
              false,
              'bind(): You are binding a component method to the component. ' +
                'React does this for you automatically in a high-performance ' +
                'way, so you can safely remove this call. See %s',
              componentName
            )
          : void 0
        return boundMethod
      }
      var reboundMethod = _bind.apply(boundMethod, arguments)
      reboundMethod.__reactBoundContext = component
      reboundMethod.__reactBoundMethod = method
      reboundMethod.__reactBoundArguments = args
      return reboundMethod
    }
  }
  return boundMethod
}

/**
 * Binds all auto-bound methods in a component.
 *
 * @param {object} component Component whose method is going to be bound.
 */
function bindAutoBindMethods(component) {
  var pairs = component.__reactAutoBindPairs
  for (var i = 0; i < pairs.length; i += 2) {
    var autoBindKey = pairs[i]
    var method = pairs[i + 1]
    component[autoBindKey] = bindAutoBindMethod(component, method)
  }
}

/**
 * Add more to the ReactClass base class. These are all legacy features and
 * therefore not already part of the modern ReactComponent.
 */
var ReactClassMixin = {
  /**
   * TODO: This will be deprecated because state should always keep a consistent
   * type signature and the only use case for this, is to avoid that.
   */
  replaceState: function (newState, callback) {
    this.updater.enqueueReplaceState(this, newState)
    if (callback) {
      this.updater.enqueueCallback(this, callback, 'replaceState')
    }
  },

  /**
   * Checks whether or not this composite component is mounted.
   * @return {boolean} True if mounted, false otherwise.
   * @protected
   * @final
   */
  isMounted: function () {
    return this.updater.isMounted(this)
  }
}

var ReactClassComponent = function () {}
_assign(
  ReactClassComponent.prototype,
  ReactComponent.prototype,
  ReactClassMixin
)

/**
 * Module for creating composite components.
 *
 * @class ReactClass
 */
var ReactClass = {
  /**
   * Creates a composite component class given a class specification.
   *
   * @param {object} spec Class specification (which must define `render`).
   * @return {function} Component constructor function.
   * @public
   */
  createClass: function (spec) {
    var Constructor = function (props, context, updater) {
      // This constructor gets overridden by mocks. The argument is used
      // by mocks to assert on what gets mounted.

      if (process.env.NODE_ENV !== 'production') {
        process.env.NODE_ENV !== 'production'
          ? warning(
              this instanceof Constructor,
              'Something is calling a React component directly. Use a factory or ' +
                'JSX instead. See: https://fb.me/react-legacyfactory'
            )
          : void 0
      }

      // Wire up auto-binding
      if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this)
      }

      this.props = props
      this.context = context
      this.refs = emptyObject
      this.updater = updater || ReactNoopUpdateQueue

      this.state = null

      // ReactClasses doesn't have constructors. Instead, they use the
      // getInitialState and componentWillMount methods for initialization.

      var initialState = this.getInitialState ? this.getInitialState() : null
      if (process.env.NODE_ENV !== 'production') {
        // We allow auto-mocks to proceed as if they're returning null.
        if (
          initialState === undefined &&
          this.getInitialState._isMockFunction
        ) {
          // This is probably bad practice. Consider warning here and
          // deprecating this convenience.
          initialState = null
        }
      }
      !(typeof initialState === 'object' && !Array.isArray(initialState))
        ? process.env.NODE_ENV !== 'production'
          ? invariant(
              false,
              '%s.getInitialState(): must return an object or null',
              Constructor.displayName || 'ReactCompositeComponent'
            )
          : invariant(false)
        : void 0

      this.state = initialState
    }
    Constructor.prototype = new ReactClassComponent()
    Constructor.prototype.constructor = Constructor
    Constructor.prototype.__reactAutoBindPairs = []

    injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor))

    mixSpecIntoComponent(Constructor, spec)

    // Initialize the defaultProps property after all mixins have been merged.
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps()
    }

    if (process.env.NODE_ENV !== 'production') {
      // This is a tag to indicate that the use of these method names is ok,
      // since it's used with createClass. If it's not, then it's likely a
      // mistake so we'll warn you to use the static property, property
      // initializer or constructor respectively.
      if (Constructor.getDefaultProps) {
        Constructor.getDefaultProps.isReactClassApproved = {}
      }
      if (Constructor.prototype.getInitialState) {
        Constructor.prototype.getInitialState.isReactClassApproved = {}
      }
    }

    !Constructor.prototype.render
      ? process.env.NODE_ENV !== 'production'
        ? invariant(
            false,
            'createClass(...): Class specification must implement a `render` method.'
          )
        : invariant(false)
      : void 0

    if (process.env.NODE_ENV !== 'production') {
      process.env.NODE_ENV !== 'production'
        ? warning(
            !Constructor.prototype.componentShouldUpdate,
            '%s has a method called ' +
              'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
              'The name is phrased as a question because the function is ' +
              'expected to return a value.',
            spec.displayName || 'A component'
          )
        : void 0
      process.env.NODE_ENV !== 'production'
        ? warning(
            !Constructor.prototype.componentWillRecieveProps,
            '%s has a method called ' +
              'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
            spec.displayName || 'A component'
          )
        : void 0
    }

    // Reduce time spent doing lookups by setting these on the prototype.
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null
      }
    }

    return Constructor
  },

  injection: {
    injectMixin: function (mixin) {
      injectedMixins.push(mixin)
    }
  }
}

module.exports = ReactClass
